﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Skugo.Shared;
using System.IO;
using System.Diagnostics;

namespace Skugo
{
    /// <summary>
    /// Communicates with the link and is responsible for sending and receiving
    /// packets so that the plugins can communicate with their counterparts.
    /// This class is separated from the IHost/IConnection interface to their implementation easier.
    /// </summary>
    public class PacketRouter
    {
        /// <summary>
        /// Store the link that we communicate with.
        /// </summary>
        private Connection Connection;

        /// <summary>
        /// The plugin container that contains the list of all local plugins.
        /// </summary>
        private PluginContainer LocalPlugins;

        /// <summary>
        /// Store the size of the current packet.
        /// </summary>
        private PacketHeader CurrentPacket;

        /// <summary>
        /// Store all the read in data.
        /// </summary>
        private List<Byte> Data = new List<Byte>();

        public PacketRouter(Connection connection, PluginContainer local)
        {
            // Store the connection
            this.Connection = connection;

            // Store the local and global plugin containers
            this.LocalPlugins = local;

            // Initialize the data size
            this.CurrentPacket.DataSize = PacketHeader.InvalidDataSize;
        }

        public void ProcessAllIncoming()
        {
            // Store the data
            Byte[] data = null;

            // Attempt to read all data in from the link...
            while ((data = this.Connection.Read()) != null)
            {
                // Add the data to the end of the list
                this.Data.AddRange(data);
                data = null;
            }

            // Should we continue reading packets in? (for the below loop)
            bool contiueParsingPackets = false;

            do
            {
                // Get the array data
                data = this.Data.ToArray();

                // Assume we have not read any packets in yet
                contiueParsingPackets = false;

                // If we're currently not working on a packet and we
                // at least have enough data for to read a header size...
                if (this.CurrentPacket.DataSize == PacketHeader.InvalidDataSize && this.Data.Count > PacketHeader.Size)
                {
                    // Create a binary reader to parse the header data
                    BinaryReader headerReader = data.ToBinaryReader();

                    // Read packet header...
                    this.CurrentPacket.DataSize = headerReader.ReadUInt32();
                    this.CurrentPacket.Type = (PacketType)headerReader.ReadByte();
                    this.CurrentPacket.InstanceGuid = headerReader.ReadInt64();
                }

                // Are we currently working on a packet, and do we have enough data to make a full packet?
                Boolean fullPacket = this.CurrentPacket.DataSize != PacketHeader.InvalidDataSize &&
                                     this.Data.Count >= (this.CurrentPacket.DataSize + PacketHeader.Size);

                // Check if we are now reading a packet and if we have enough data to read the packet
                if (fullPacket)
                {
                    // Copy the data into a newly created packet buffer
                    byte[] newData = new Byte[this.CurrentPacket.DataSize];
                    Buffer.BlockCopy(data, (Int32)PacketHeader.Size, newData, 0, (Int32)this.CurrentPacket.DataSize);


                    // Find the corresponding local plugin and route the data to it
                    Plugin local = this.LocalPlugins.GetPlugin(this.CurrentPacket.InstanceGuid);

                    // If we found the local plugin
                    if (local != null)
                    {
                        // Route the packet to the local plugin
                        local.Receive(newData, this.CurrentPacket.Type);
                    }
                    else
                    {
                        // Throw an error since this is not ok!
                        throw new Exception("A message came for an invalid plugin");
                    }

                    // Clear all this packet's data from the buffer
                    this.Data.RemoveRange(0, (int)(PacketHeader.Size + this.CurrentPacket.DataSize));

                    // Reset the current packet size
                    this.CurrentPacket.DataSize = PacketHeader.InvalidDataSize;

                    // We read a packet, we should attempt to continue!
                    contiueParsingPackets = true;
                }
            }
            // Loop until we stopped parsing packets
            while (contiueParsingPackets);
        }
    }
}
